/**
 * Copyright (c) 2012
 */
package com.bolt.support.spring.jpa.filter;

import com.bolt.common.convert.Convert;
import com.bolt.common.lang.Assert;
import com.bolt.common.reflect.ReflectionUtil;
import com.bolt.common.utils.DateUtil;
import com.bolt.common.utils.StrUtil;
import com.bolt.common.web.RequestUtil;
import com.bolt.support.query.conditions.domain.MatchType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;


/**
 * 与具体ORM实现无关的属性过滤条件封装类, 主要记录页面中简单的搜索过滤条件. 用于页面表单传入字符串形式条件，然后转换处理为DAO层面识别的SQL条件
 * 页面表单元素示例：
 * <ul>
 * <li>search['CN_a_OR_b']</li>
 * <li>search['EQ_id']</li>
 * <li>search['CN_user.name']</li>
 * </ul>
 * <p>
 * FORM传递表单参数规则： <br/>
 * 1, 第一部分：以"search[]"作为查询参数标识 <br/>
 * 2, 第二部分：查询类型，@see #MatchType <br/>
 * 3, 第三部分：id_OR_email，category，state, user.userprofile为属性名称,一般对应于Hibernate
 * Entity对应属性,可以以_OR_分隔多个属性进行OR查询
 * </p>
 * <p>
 * 上述拼装字符串形式主要用于JSP页面form表单元素name属性值,如果是Java代码层面追加过滤条件,一般直接用构造函数:
 * PropertyFilter(final MatchType matchType, final String propertyName, final
 * Object matchValue)
 * </p>
 */
public class PropertyFilter {

    private final static Logger logger = LoggerFactory.getLogger(PropertyFilter.class);


    /**
     * 匹配类型
     */
    private MatchType matchType = null;

    /**
     * 匹配值
     */
    private Object matchValue = null;

    /**
     * 匹配属性类型
     * 限制说明：如果是多个属性则取第一个
     */
    private Class propertyClass = null;

    /**
     * 属性名称数组,
     */
    private String leftProperty = null;


    private String rightProperty = null;

    private String alias;

    private String test;
    /**
     * 集合类型子查询,如查询包含某个商品的所有订单列表,如order上面有个List集合products对象，则可以类似这样:search['EQ_products.code']
     * 限制说明：框架只支持当前主对象直接定义的集合对象集合查询，不支持再多层嵌套
     */
    private Class subQueryCollectionPropetyType;


    /**
     * @param filterName 比较属性字符串,含待比较的比较类型、属性值类型及属性列表.
     * @param values     待比较的值.
     */
    public PropertyFilter(Class<?> entityClass, String filterName, String... values) {

        String matchTypeCode = StrUtil.subBefore(filterName, "_", true);

        try {
            matchType = Enum.valueOf(MatchType.class, matchTypeCode);
        } catch (RuntimeException e) {
            throw new IllegalArgumentException("filter名称" + filterName + "没有按规则编写,无法得到属性比较类型.", e);
        }

        String propertyNameStr = StrUtil.subAfter(filterName, "_", true);
        Assert.notBlank(propertyNameStr, "filter名称" + filterName + "没有按规则编写,无法得到属性名称.");
        leftProperty = propertyNameStr;
        try {
            Method method = null;
            String[] namesSplits = StrUtil.split(leftProperty, ".");
            if (namesSplits.length == 1) {
                method = ReflectionUtil.getGetterMethod(entityClass, namesSplits[0]);
            } else {
                Class<?> retClass = entityClass;
                for (String nameSplit : namesSplits) {
                    method = ReflectionUtil.getGetterMethod(retClass, nameSplit);
                    retClass = method.getReturnType();
                    if (Collection.class.isAssignableFrom(retClass)) {
                        Type genericReturnType = method.getGenericReturnType();
                        if (genericReturnType instanceof ParameterizedType) {
                            retClass = (Class<?>) ((ParameterizedType) genericReturnType).getActualTypeArguments()[0];
                            subQueryCollectionPropetyType = retClass;
                        }
                    }
                }
            }
            propertyClass = method.getReturnType();
        } catch (Exception e) {
            throw new IllegalArgumentException("无效对象属性定义：" + entityClass + ":" + leftProperty, e);
        }

        if (values.length == 1) {
            if (matchType.equals(MatchType.IN)) {
                String value = values[0];
                values = value.split(",");
            }
            if (values.length == 1) {
                this.matchValue = parseMatchValueByClassType(propertyClass, values[0]);
            } else {
                Object[] matchValues = new Object[values.length];
                for (int i = 0; i < values.length; i++) {
                    matchValues[i] = parseMatchValueByClassType(propertyClass, values[i]);
                }
                this.matchValue = matchValues;
            }

        }

    }

    private Object parseMatchValueByClassType(Class propertyClass, String value) {
        if ("NULL".equalsIgnoreCase(value)) {
            return value;
        }
        if (Enum.class.isAssignableFrom(propertyClass)) {
            return Enum.valueOf(propertyClass, value);
        } else if (propertyClass.equals(Boolean.class) || matchType.equals(MatchType.NN) || matchType.equals(MatchType.NU)) {
            return new Boolean(Convert.toBool(value));
        } else if (propertyClass.equals(Date.class) && !matchType.equals(MatchType.BT)) {
            return DateUtil.parseDate(value, null, DateUtil.MULTI_FORMAT);
        } else if (propertyClass.equals(Date.class) && matchType.equals(MatchType.BT)) {
            return DateUtil.parseBetweenDates(value);
        } else if (propertyClass.equals(LocalDateTime.class) && matchType.equals(MatchType.BT)) {
            Date[] betweenDate = DateUtil.parseBetweenDates(value);
            return new LocalDateTime[]{DateUtil.asLocalDateTime(betweenDate[0]), DateUtil.asLocalDateTime(betweenDate[1])};
        } else if (propertyClass.equals(LocalDate.class) && matchType.equals(MatchType.BT)) {
            Date[] betweenDate = DateUtil.parseBetweenDates(value);
            return new LocalDate[]{DateUtil.asLocalDate(betweenDate[0]), DateUtil.asLocalDate(betweenDate[1])};
        } else {
            return Convert.convert(propertyClass, value);
        }
    }

    /**
     * Java程序层直接构造过滤器对象, 如filters.add(new PropertyFilter(MatchType.EQ, "code",
     * code));
     *
     * @param matchType
     * @param leftProperty
     * @param matchValue
     */
    public PropertyFilter(final MatchType matchType, final String leftProperty, final Object matchValue) {
        this.matchType = matchType;
        this.leftProperty = leftProperty;
        this.matchValue = matchValue;
    }


    public PropertyFilter(final String leftProperty, final MatchType matchType, final String rightProperty) {
        this.matchType = matchType;
        this.leftProperty = leftProperty;
        this.rightProperty = rightProperty;
    }

    public PropertyFilter(final MatchType matchType, String test, final String leftProperty) {
        this.test = test;
        this.matchType = matchType;
        this.leftProperty = leftProperty;
    }

    /**
     * 从HttpRequest中创建PropertyFilter列表
     * PropertyFilter命名规则为Filter属性前缀_比较类型属性类型_属性名.
     */
    public static List<PropertyFilter> buildFiltersFromHttpRequest(Class<?> entityClass, HttpServletRequest request) {

        List<PropertyFilter> filterList = new ArrayList<PropertyFilter>();

        // 从request中获取含属性前缀名的参数,构造去除前缀名后的参数Map.
        Map<String, String[]> filterParamMap = getParametersStartingWith(request, "search['", "']");

        // 分析参数Map,构造PropertyFilter列表
        for (Map.Entry<String, String[]> entry : filterParamMap.entrySet()) {
            String filterName = entry.getKey();
            String[] values = entry.getValue();
            if (values == null || values.length == 0) {
                continue;
            }

            if (values.length == 1) {
                String value = values[0];
                // 如果value值为空,则忽略此filter.
                if (StrUtil.isNotBlank(value)) {
                    PropertyFilter filter = new PropertyFilter(entityClass, filterName, value);
                    filterList.add(filter);
                }
            } else {
                String[] valuesArr = values;
                // 如果value值为空,则忽略此filter.
                if (valuesArr.length > 0) {
                    Set<String> valueSet = new HashSet<String>();
                    for (String value : valuesArr) {
                        if (StrUtil.isNotBlank(value)) {
                            valueSet.add(value);
                        }
                    }
                    if (valueSet.size() > 0) {
                        String[] realValues = new String[valueSet.size()];
                        int cnt = 0;
                        for (String v : valueSet) {
                            realValues[cnt++] = v;
                        }
                        PropertyFilter filter = new PropertyFilter(entityClass, filterName, realValues);
                        filterList.add(filter);
                    }

                }
            }

        }
        return filterList;
    }


    /**
     * 获取比较方式.
     */
    public MatchType getMatchType() {
        return matchType;
    }

    /**
     * 获取比较值.
     */
    public Object getMatchValue() {
        return matchValue;
    }

    /**
     * 获取比较属性名称列表.
     */
    public String getLeftProperty() {
        return leftProperty;
    }


    public String getRightProperty() {
        return rightProperty;
    }

    public String getAlias() {
        return alias;
    }

    public void setAlias(String alias) {
        this.alias = alias;
    }

    public void setMatchValue(Object matchValue) {
        this.matchValue = matchValue;
    }

    /**
     * 构造一个缺省过滤集合.
     */
    public static List<PropertyFilter> buildDefaultFilterList() {
        return new ArrayList<PropertyFilter>();
    }

    public Class getPropertyClass() {
        return propertyClass;
    }

    public Class getSubQueryCollectionPropetyType() {
        return subQueryCollectionPropetyType;
    }

    private static Map<String, String[]> getParametersStartingWith(HttpServletRequest request, String prefix, String suffix) {
        return RequestUtil.getParametersStartingWith(request, prefix, suffix);
    }

    public String getTest() {
        return test;
    }

    public void setTest(String test) {
        this.test = test;
    }
}
